﻿using System;
using System.Net;
using Azure.Online.Proxy.Actions;
using Azure.Online.Proxy.Server;

namespace Azure.Online.Proxy {
    public class ProxyService {

        private HttpServer _listener;

        public void Start() {
            if (_listener == null) {
                _listener = new HttpServer(Config.ServerPort);
                _listener.Request += (sender, args) => ProcessRequest(args.Context);
                _listener.Start();
            }
        }

        public void Stop() {
            try {
                _listener.Stop();
                _listener = null;
            } catch {

            }
        }

        private void ProcessRequest(HttpServerContext context) {
            ActionResult action;
            try {
                action = GetActionResult(context);
                context.Response.Headers["Access-Control-Allow-Origin"] = "*";
                context.Response.Headers["Access-Control-Expose-Headers"] = "Date, x-ms-version, x-ms-request-id, x-ms-lease-id";
            } catch {
                action = new InternalServerErrorActionResult();
            }
            action.Execute(context);
        }

        private ActionResult GetActionResult(HttpServerContext context) {
            if (context.Request.Verb == "OPTIONS") {
                return new OptionsActionResult();
            }

            var uri = context.Request.Url;
            var localPath = uri.LocalPath.ToLower().TrimEnd('/');

            if (localPath.StartsWith("/download")) {
                return ProcessDownloadRequest(context);
            }

            var targetUrl = context.Request.Headers["x-azure-online-url"];
            var targetDate = context.Request.Headers["x-azure-online-date"];
            var contentLength = context.Request.Headers["x-azure-online-content-length"];
            var contentType = context.Request.Headers["x-azure-online-content-type"];
            var base64 = context.Request.Headers["x-azure-online-accept-base64"];

            var request = (HttpWebRequest)WebRequest.Create(targetUrl);
            request.Method = context.Request.Verb;

            if (!string.IsNullOrWhiteSpace(targetDate)) {
                request.Date = DateTime.Parse(targetDate);
            }

            if (!string.IsNullOrWhiteSpace(contentType)) {
                request.ContentType = contentType;
            }

            if (!string.IsNullOrWhiteSpace(contentLength)) {
                request.ContentLength = long.Parse(contentLength);
            }

            foreach (string headerName in context.Request.Headers.Keys) {
                switch (headerName) {
                    case "Accept":
                        request.Accept = context.Request.Headers[headerName];
                        break;
                    case "Accept-Charset":
                    case "Authorization":
                    case "DataServiceVersion":
                    case "MaxDataServiceVersion":
                    case "ETag":
                    case "If-Match":
                        request.Headers[headerName] = context.Request.Headers[headerName];
                        break;
                    default:
                        if (headerName.StartsWith("x-ms-") || headerName.StartsWith("Accept")) {
                            request.Headers[headerName] = context.Request.Headers[headerName];
                        }
                        break;
                }
            }
            return ProcessWebRequest(request, context, response => new ProxyActionResult(response) { AcceptBase64 = base64 == "true" });
        }

        private ActionResult ProcessDownloadRequest(HttpServerContext context) {
            var query = context.Request.QueryString;
            var downloadUrl = query["u"];
            var date = query["d"];
            var msDate = query["md"];
            var version = query["v"];
            var authorization = query["a"];
            var name = query["n"];
            var view = query["view"] == "true";
            var mimeType = query["mimeType"];

            var request = (HttpWebRequest)WebRequest.Create(downloadUrl);
            if (!string.IsNullOrWhiteSpace(date)) {
                request.Date = DateTime.Parse(date);
            }
            if (!string.IsNullOrWhiteSpace(msDate)) {
                request.Headers["x-ms-date"] = msDate;
            }
            request.Headers["x-ms-version"] = version;
            request.Headers["Authorization"] = authorization;
            return ProcessWebRequest(request, context, response => new DownloadActionResult(response, !view ? name : null) { ContentType = mimeType });
        }

        private static ActionResult ProcessWebRequest(HttpWebRequest request, HttpServerContext context, Func<HttpWebResponse, ProxyActionResult> resultBuilder) {
            try {
                if (request.ContentLength >= 0) {
                    var requestStream = request.GetRequestStream();
                    StreamUtils.CopyStream(context.Request.InputStream, requestStream, request.ContentLength);
                }
                var response = request.GetResponse();
                return resultBuilder((HttpWebResponse)response);
            } catch (WebException exc) {
                switch (exc.Status) {
                    case WebExceptionStatus.ProtocolError:
                        return new ProxyActionResult((HttpWebResponse)exc.Response);
                    case WebExceptionStatus.NameResolutionFailure:
                        return new HttpNotFoundResult();
                    default:
                        throw;
                }
            }
        }
    }
}
